package com.onpositive.data.kvstorage;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.util.ArrayList;

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.onpositive.data.AbstractReader;
import com.onpositive.data.IPropertyConstants;
import com.onpositive.data.IPropertyMetadata;
import com.onpositive.data.diskstorage.AbstractObjectCollection;
import com.onpositive.data.diskstorage.StoredMetadata;
import com.onpositive.data.serializer.writers.AbstractWriter;
import com.onpositive.datamap.core.IDataObject;
import com.onpositive.datamap.core.impl.AbstractDataProperty;
import com.onpositive.datamap.item.SingleDataObject;
import com.onpositive.datamap.values.impl.ValueSequence;

public class KeyValueBasedCollection extends AbstractObjectCollection {

	private static final String INDEX = "$index";
	protected IKeyValueAccess access;
	protected int state;

	@SuppressWarnings("unchecked")
	public KeyValueBasedCollection(IKeyValueAccess access) {
		super(0);
		this.access = access;
		byte[] bs = access.get(INDEX);
		if (bs != null) {
			ByteArrayInputStream bi = new ByteArrayInputStream(bs);
			Hessian2Input mi = new Hessian2Input(bi);
			try {
				ArrayList<IPropertyMetadata> m = (ArrayList<IPropertyMetadata>) mi
						.readObject();
				for (IPropertyMetadata mq : m) {
					addMetadata(mq);
				}
				state = mi.readInt();
				dataSize = mi.readInt();
			} catch (Exception e) {
				throw new IllegalStateException(e);
			}
		}
	}
	
	public void storeObject(IDataObject m){
		SingleDataObject mq=new SingleDataObject(m);
		byte[] byteArray = mq.toByteArray();
		access.put("!"+mq.getUID(), byteArray);
	}
	
	public SingleDataObject getObject(int num){
		String value = (String) getValue(num, getProperty(IPropertyConstants.DOCUMENT_NAME_PROPERTY_METADATA_ID));
		byte[] bs = access.get("!"+value.replace('/', '-'));
		if (bs!=null){
			return SingleDataObject.from(bs);
		}
		return null;		
	}
	
	@Override
	public void setValue(IDataObject obj, AbstractDataProperty p, Object value) {
		super.setValue(obj, p, value);
		store();
		IPropertyMetadata meta = getProperty(p.getId());
		AbstractReader<?> reader = readerMap.get(meta);
		if (reader == null)
			reader = initReader(meta);
		if (reader == null) {
			throw new IllegalArgumentException();
		}
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		DataOutputStream stream = new DataOutputStream(byteArrayOutputStream);
		try {
			AbstractWriter<?> writer = reader.getWriter();
			int size = size();
			for (int a=0;a<size;a++){
				writer.checkedAppend(reader.getObject(a));
			}
			writer.serialize(stream);
			stream.close();
			
			access.put(meta.id(), byteArrayOutputStream.toByteArray());
			
		} catch (Exception e) {
			throw new IllegalStateException();
		}
		
		SingleDataObject object = getObject(obj.getCaption());
		object.setProperty(p,value);
		storeObject(object);
	}

	@Override
	protected AbstractReader<?> initReader(IPropertyMetadata meta) {
		String id = meta.id();
		byte[] bs = access.get(id);
		return initLocal(meta, bs);
	}

	public void setIndex(IPropertyMetadata meta, Object[] values) {
		int k=this.dataSize;
		if (dataSize < values.length) {
			dataSize = values.length;
			if(k!=dataSize){
				store();
			}
		}
		AbstractReader<?> reader = readerMap.get(meta);
		if (reader == null)
			reader = initReader(meta);
		if (reader == null) {
			throw new IllegalArgumentException();
		}
		for (int a = 0; a < dataSize; a++) {
			reader.setValue(a, values[a]);
		}
		AbstractWriter<?> writer = reader.getWriter();
		for (int a = 0; a < values.length; a++) {
			if (values[a] instanceof ValueSequence) {
				ValueSequence s = (ValueSequence) values[a];
				StringBuilder bld = new StringBuilder();
				Object[] parts = s.getParts();
				int i = 0;
				for (Object q : parts) {
					bld.append(q.toString());
					if (i != parts.length - 1) {
						bld.append("_#_");
					}
					i++;
				}
				writer.checkedAppend(bld.toString());
			} else {
				writer.checkedAppend(values[a]);
			}
		}
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		DataOutputStream stream = new DataOutputStream(byteArrayOutputStream);
		try {
			writer.serialize(stream);
			stream.close();
			access.put(meta.id(), byteArrayOutputStream.toByteArray());
			
		} catch (Exception e) {
			throw new IllegalStateException();
		}
	}

	public void store() {
		try {
			ByteArrayOutputStream os = new ByteArrayOutputStream();
			Hessian2Output o = new Hessian2Output(os);
			o.writeObject(metadataArray);
			o.writeInt(state);
			o.writeInt(dataSize);
			o.close();
			access.put(INDEX, os.toByteArray());
		} catch (Exception e) {
			throw new IllegalStateException(e);
		}
	}
	
	

	public void setIndex(String id, Object[] mma) {
		setIndex(getProperty(id), mma);
	}

	public byte[] getReaderData(String propId) {
		String id = propId;
		byte[] bs = access.get(id);
		return bs;
	}

	public SingleDataObject getObject(String id) {
		byte[] bs = access.get("!"+id.replace('/', '-'));
		if (bs!=null){
			return SingleDataObject.from(bs);
		}
		return null;
	}

	@Override
	public void updateMeta(IPropertyMetadata m) {
		for (IPropertyMetadata z:metadataArray){
			if (z.id().equals(m.id())){
				StoredMetadata qqq=(StoredMetadata) z;
				qqq.update((StoredMetadata) m);
			}
		}
		store();
	}
	
	@Override
	public void setValue(int num, IPropertyMetadata meta, Object value) {
		super.setValue(num, meta, value);
		AbstractReader<?> reader = readerMap.get(meta);
		if (reader == null)
			reader = initReader(meta);
		if (reader == null) {
			throw new IllegalArgumentException();
		}
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		DataOutputStream stream = new DataOutputStream(byteArrayOutputStream);
		try {
			AbstractWriter<?> writer = reader.getWriter();
			int size = size();
			for (int a=0;a<size;a++){
				writer.checkedAppend(reader.getObject(a));
			}
			writer.serialize(stream);
			stream.close();
			
			access.put(meta.id(), byteArrayOutputStream.toByteArray());
			
		} catch (Exception e) {
			throw new IllegalStateException();
		}
		
		SingleDataObject object = getObject(num);
		if (meta.id().equals(IPropertyConstants.ACTUALITY_PROPERTY_METADATA_ID)){
			object.setPrimaryObject((Boolean) value);
		}
		storeObject(object);
	}

}